#pragma once
#include "basedefs.hpp"
#include <functional>

namespace opengl {

namespace api {
// Basic OpenGL functions
void (*glClear)(u32 mask) = nullptr;
void (*glClearColor)(f32 red, f32 green, f32 blue, f32 alpha) = nullptr;
void (*glEnable)(u32 cap) = nullptr;
void (*glDisable)(u32 cap) = nullptr;
void (*glViewport)(i32 x, i32 y, i32 width, i32 height) = nullptr;
void (*glScissor)(i32 x, i32 y, i32 width, i32 height) = nullptr;
void (*glBlendFunc)(u32 sfactor, u32 dfactor) = nullptr;
void (*glBlendFuncSeparate)(u32 srcRGB, u32 dstRGB, u32 srcAlpha, u32 dstAlpha) = nullptr;
void (*glDepthFunc)(u32 func) = nullptr;
void (*glDepthMask)(u8 flag) = nullptr;
void (*glStencilFunc)(u32 func, i32 ref, u32 mask) = nullptr;
void (*glStencilMask)(u32 mask) = nullptr;
void (*glStencilOp)(u32 fail, u32 zfail, u32 zpass) = nullptr;
void (*glCullFace)(u32 mode) = nullptr;
void (*glFrontFace)(u32 mode) = nullptr;

// Textures
void (*glCreateTextures)(u32 target, i32 n, u32 *textures) = nullptr;
void (*glTextureStorage2D)(u32 texture, i32 levels, u32 internalformat, i32 width, i32 height) = nullptr;
void (*glTextureSubImage2D)(u32 texture, i32 level, i32 xoffset, i32 yoffset, i32 width, i32 height, u32 format,
                            u32 type, const void *pixels) = nullptr;
void (*glTextureParameteri)(u32 texture, u32 pname, i32 param) = nullptr;
void (*glTextureParameterf)(u32 texture, u32 pname, f32 param) = nullptr;
void (*glGenerateTextureMipmap)(u32 texture) = nullptr;
void (*glDeleteTextures)(i32 n, const u32 *textures) = nullptr;
void (*glActiveTexture)(u32 unit) = nullptr;
void (*glBindTextureUnit)(u32 unit, u32 texture) = nullptr;

// Buffers
void (*glCreateBuffers)(i32 n, u32 *buffers) = nullptr;
void (*glDeleteBuffers)(i32 n, const u32 *buffers) = nullptr;
void (*glBindBuffer)(u32 target, u32 buffer) = nullptr;
void (*glBindBufferBase)(u32 target, u32 index, u32 buffer) = nullptr;
void (*glNamedBufferStorage)(u32 buffer, i32 size, const void *data, u32 flags) = nullptr;
void (*glNamedBufferSubData)(u32 buffer, i32 offset, i32 size, const void *data) = nullptr;
void (*glGetNamedBufferParameteriv)(u32 buffer, u32 pname, i32 *params) = nullptr;
void (*glCopyNamedBufferSubData)(u32 readBuffer, u32 writeBuffer, i32 readOffset, i32 writeOffset, i32 size) = nullptr;
u32 (*glCreateShader)(u32 type) = nullptr;
void (*glDeleteShader)(u32 shader) = nullptr;
void (*glShaderSource)(u32 shader, i32 count, const char **string, const i32 *length) = nullptr;
void (*glCompileShader)(u32 shader) = nullptr;
void (*glGetShaderiv)(u32 shader, u32 pname, i32 *params) = nullptr;
void (*glGetShaderInfoLog)(u32 shader, i32 bufSize, i32 *length, char *infoLog) = nullptr;
void (*glGetShaderSource)(u32 shader, i32 bufSize, i32 *length, char *source) = nullptr;
void (*glDebugMessageCallback)(void (*callback)(u32 source, u32 type, u32 id, u32 severity, i32 length,
                                                const char *message, const void *userParam),
                               const void *userParam) = nullptr;
u32 (*glCreateProgram)() = nullptr;
void (*glDeleteProgram)(u32 program) = nullptr;
void (*glAttachShader)(u32 program, u32 shader) = nullptr;
void (*glDetachShader)(u32 program, u32 shader) = nullptr;
void (*glLinkProgram)(u32 program) = nullptr;
void (*glGetProgramiv)(u32 program, u32 pname, i32 *params) = nullptr;
void (*glGetProgramInfoLog)(u32 program, i32 bufSize, i32 *length, char *infoLog) = nullptr;
void (*glUseProgram)(u32 program) = nullptr;
void (*glGetAttachedShaders)(u32 program, i32 maxCount, i32 *count, u32 *shaders) = nullptr;
i32 (*glGetUniformLocation)(u32 program, const char *name) = nullptr;
void (*glProgramUniform1i)(u32 program, i32 location, i32 v0) = nullptr;
void (*glProgramUniform1ui)(u32 program, i32 location, u32 v0) = nullptr;
void (*glProgramUniform1i64)(u32 program, i32 location, i64 v0) = nullptr;
void (*glProgramUniform1ui64)(u32 program, i32 location, u64 v0) = nullptr;
void (*glProgramUniform1f)(u32 program, i32 location, f32 v0) = nullptr;
void (*glProgramUniform1d)(u32 program, i32 location, f64 v0) = nullptr;
void (*glProgramUniform2fv)(u32 program, i32 location, i32 count, const f32 *value) = nullptr;
void (*glProgramUniform3fv)(u32 program, i32 location, i32 count, const f32 *value) = nullptr;
void (*glProgramUniform4fv)(u32 program, i32 location, i32 count, const f32 *value) = nullptr;
void (*glProgramUniformMatrix2fv)(u32 program, i32 location, i32 count, u8 transposed, const f32 *value) = nullptr;
void (*glProgramUniformMatrix3fv)(u32 program, i32 location, i32 count, u8 transposed, const f32 *value) = nullptr;
void (*glProgramUniformMatrix4fv)(u32 program, i32 location, i32 count, u8 transposed, const f32 *value) = nullptr;
void (*glEnableVertexArrayAttrib)(u32 vaobj, u32 index) = nullptr;
void (*glVertexArrayVertexBuffer)(u32 vaobj, u32 bindingindex, u32 buffer, i32 offset, i32 stride) = nullptr;
void (*glVertexArrayAttribFormat)(u32 vaobj, u32 attribindex, i32 size, u32 type, u8 normalized,
                                  u32 relativeoffset) = nullptr;
void (*glVertexArrayAttribBinding)(u32 vaobj, u32 attribindex, u32 bindingindex) = nullptr;
void (*glVertexArrayElementBuffer)(u32 vaobj, u32 buffer) = nullptr;
void (*glBindVertexArray)(u32 array) = nullptr;
void (*glCreateVertexArrays)(i32 n, u32 *arrays) = nullptr;
void (*glDeleteVertexArrays)(i32 n, const u32 *arrays) = nullptr;
void (*glDrawArrays)(u32 mode, i32 first, i32 count) = nullptr;
void (*glDrawElements)(u32 mode, i32 count, u32 type, const void *indices) = nullptr;
void (*glPolygonMode)(u32 face, u32 mode) = nullptr;
} // namespace api

OPENGLAPI bool load_functions(voidptr (*load_function)(const char *)) {
    using namespace api;

    // Basic OpenGL functions
    glClear = reinterpret_cast<void (*)(u32)>(load_function("glClear"));
    glClearColor = reinterpret_cast<void (*)(f32, f32, f32, f32)>(load_function("glClearColor"));
    glEnable = reinterpret_cast<void (*)(u32)>(load_function("glEnable"));
    glDisable = reinterpret_cast<void (*)(u32)>(load_function("glDisable"));
    glViewport = reinterpret_cast<void (*)(i32, i32, i32, i32)>(load_function("glViewport"));
    glScissor = reinterpret_cast<void (*)(i32, i32, i32, i32)>(load_function("glScissor"));
    glBlendFunc = reinterpret_cast<void (*)(u32, u32)>(load_function("glBlendFunc"));
    glBlendFuncSeparate = reinterpret_cast<void (*)(u32, u32, u32, u32)>(load_function("glBlendFuncSeparate"));
    glDepthFunc = reinterpret_cast<void (*)(u32)>(load_function("glDepthFunc"));
    glDepthMask = reinterpret_cast<void (*)(u8)>(load_function("glDepthMask"));
    glStencilFunc = reinterpret_cast<void (*)(u32, i32, u32)>(load_function("glStencilFunc"));
    glStencilMask = reinterpret_cast<void (*)(u32)>(load_function("glStencilMask"));
    glStencilOp = reinterpret_cast<void (*)(u32, u32, u32)>(load_function("glStencilOp"));
    glCullFace = reinterpret_cast<void (*)(u32)>(load_function("glCullFace"));
    glFrontFace = reinterpret_cast<void (*)(u32)>(load_function("glFrontFace"));

    // Textures
    glCreateTextures = reinterpret_cast<void (*)(u32, i32, u32 *)>(load_function("glCreateTextures"));
    glTextureStorage2D = reinterpret_cast<void (*)(u32, i32, u32, i32, i32)>(load_function("glTextureStorage2D"));
    glTextureSubImage2D = reinterpret_cast<void (*)(u32, i32, i32, i32, i32, i32, u32, u32, const void *)>(
        load_function("glTextureSubImage2D"));
    glTextureParameteri = reinterpret_cast<void (*)(u32, u32, i32)>(load_function("glTextureParameteri"));
    glTextureParameterf = reinterpret_cast<void (*)(u32, u32, f32)>(load_function("glTextureParameterf"));
    glGenerateTextureMipmap = reinterpret_cast<void (*)(u32)>(load_function("glGenerateTextureMipmap"));
    glDeleteTextures = reinterpret_cast<void (*)(i32, const u32 *)>(load_function("glDeleteTextures"));
    glActiveTexture = reinterpret_cast<void (*)(u32)>(load_function("glActiveTexture"));
    glBindTextureUnit = reinterpret_cast<void (*)(u32, u32)>(load_function("glBindTextureUnit"));

    // Buffers
    glCreateBuffers = reinterpret_cast<void (*)(i32, u32 *)>(load_function("glCreateBuffers"));
    glDeleteBuffers = reinterpret_cast<void (*)(i32, const u32 *)>(load_function("glDeleteBuffers"));
    glBindBuffer = reinterpret_cast<void (*)(u32, u32)>(load_function("glBindBuffer"));
    glBindBufferBase = reinterpret_cast<void (*)(u32, u32, u32)>(load_function("glBindBufferBase"));
    glNamedBufferStorage =
        reinterpret_cast<void (*)(u32, i32, const void *, u32)>(load_function("glNamedBufferStorage"));
    glNamedBufferSubData =
        reinterpret_cast<void (*)(u32, i32, i32, const void *)>(load_function("glNamedBufferSubData")); // 修正拼写
    glGetNamedBufferParameteriv =
        reinterpret_cast<void (*)(u32, u32, i32 *)>(load_function("glGetNamedBufferParameteriv"));
    glCopyNamedBufferSubData =
        reinterpret_cast<void (*)(u32, u32, i32, i32, i32)>(load_function("glCopyNamedBufferSubData"));

    // Shaders
    glCreateShader = reinterpret_cast<u32 (*)(u32)>(load_function("glCreateShader"));
    glDeleteShader = reinterpret_cast<void (*)(u32)>(load_function("glDeleteShader"));
    glShaderSource = reinterpret_cast<void (*)(u32, i32, const char **, const i32 *)>(load_function("glShaderSource"));
    glCompileShader = reinterpret_cast<void (*)(u32)>(load_function("glCompileShader"));
    glGetShaderiv = reinterpret_cast<void (*)(u32, u32, i32 *)>(load_function("glGetShaderiv"));
    glGetShaderInfoLog = reinterpret_cast<void (*)(u32, i32, i32 *, char *)>(load_function("glGetShaderInfoLog"));
    glGetShaderSource = reinterpret_cast<void (*)(u32, i32, i32 *, char *)>(load_function("glGetShaderSource"));

    // Debugging
    glDebugMessageCallback =
        reinterpret_cast<void (*)(void (*)(u32, u32, u32, u32, i32, const char *, const void *), const void *)>(
            load_function("glDebugMessageCallback"));

    // Programs
    glCreateProgram = reinterpret_cast<u32 (*)()>(load_function("glCreateProgram"));
    glDeleteProgram = reinterpret_cast<void (*)(u32)>(load_function("glDeleteProgram"));
    glAttachShader = reinterpret_cast<void (*)(u32, u32)>(load_function("glAttachShader"));
    glDetachShader = reinterpret_cast<void (*)(u32, u32)>(load_function("glDetachShader"));
    glLinkProgram = reinterpret_cast<void (*)(u32)>(load_function("glLinkProgram"));
    glGetProgramiv = reinterpret_cast<void (*)(u32, u32, i32 *)>(load_function("glGetProgramiv"));
    glGetProgramInfoLog = reinterpret_cast<void (*)(u32, i32, i32 *, char *)>(load_function("glGetProgramInfoLog"));
    glUseProgram = reinterpret_cast<void (*)(u32)>(load_function("glUseProgram"));
    glGetAttachedShaders = reinterpret_cast<void (*)(u32, i32, i32 *, u32 *)>(load_function("glGetAttachedShaders"));

    // Uniforms
    glGetUniformLocation = reinterpret_cast<i32 (*)(u32, const char *)>(load_function("glGetUniformLocation"));
    glProgramUniform1i = reinterpret_cast<void (*)(u32, i32, i32)>(load_function("glProgramUniform1i"));
    glProgramUniform1ui = reinterpret_cast<void (*)(u32, i32, u32)>(load_function("glProgramUniform1ui"));
    glProgramUniform1i64 = reinterpret_cast<void (*)(u32, i32, i64)>(load_function("glProgramUniform1i64"));
    glProgramUniform1ui64 = reinterpret_cast<void (*)(u32, i32, u64)>(load_function("glProgramUniform1ui64"));
    glProgramUniform1f = reinterpret_cast<void (*)(u32, i32, f32)>(load_function("glProgramUniform1f"));
    glProgramUniform1d = reinterpret_cast<void (*)(u32, i32, f64)>(load_function("glProgramUniform1d"));
    glProgramUniform2fv = reinterpret_cast<void (*)(u32, i32, i32, const f32 *)>(load_function("glProgramUniform2fv"));
    glProgramUniform3fv = reinterpret_cast<void (*)(u32, i32, i32, const f32 *)>(load_function("glProgramUniform3fv"));
    glProgramUniform4fv = reinterpret_cast<void (*)(u32, i32, i32, const f32 *)>(load_function("glProgramUniform4fv"));
    glProgramUniformMatrix2fv =
        reinterpret_cast<void (*)(u32, i32, i32, u8, const f32 *)>(load_function("glProgramUniformMatrix2fv"));
    glProgramUniformMatrix3fv =
        reinterpret_cast<void (*)(u32, i32, i32, u8, const f32 *)>(load_function("glProgramUniformMatrix3fv"));
    glProgramUniformMatrix4fv =
        reinterpret_cast<void (*)(u32, i32, i32, u8, const f32 *)>(load_function("glProgramUniformMatrix4fv"));

    // Vertex Array Objects
    glEnableVertexArrayAttrib = reinterpret_cast<void (*)(u32, u32)>(load_function("glEnableVertexArrayAttrib"));
    glVertexArrayVertexBuffer =
        reinterpret_cast<void (*)(u32, u32, u32, i32, i32)>(load_function("glVertexArrayVertexBuffer"));
    glVertexArrayAttribFormat =
        reinterpret_cast<void (*)(u32, u32, i32, u32, u8, u32)>(load_function("glVertexArrayAttribFormat"));
    glVertexArrayAttribBinding = reinterpret_cast<void (*)(u32, u32, u32)>(load_function("glVertexArrayAttribBinding"));
    glVertexArrayElementBuffer = reinterpret_cast<void (*)(u32, u32)>(load_function("glVertexArrayElementBuffer"));
    glBindVertexArray = reinterpret_cast<void (*)(u32)>(load_function("glBindVertexArray"));
    glCreateVertexArrays = reinterpret_cast<void (*)(i32, u32 *)>(load_function("glCreateVertexArrays"));
    glDeleteVertexArrays = reinterpret_cast<void (*)(i32, const u32 *)>(load_function("glDeleteVertexArrays"));

    // Draw
    glDrawArrays = reinterpret_cast<void (*)(u32, i32, i32)>(load_function("glDrawArrays"));
    glDrawElements = reinterpret_cast<void (*)(u32, i32, u32, const void *)>(load_function("glDrawElements"));
    glPolygonMode = reinterpret_cast<void (*)(u32, u32)>(load_function("glPolygonMode"));
    return true;
}

} // namespace opengl